package com.sugar.subscribe.course.module.business.service.order;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sugar.subscribe.course.framework.common.enums.UserTypeEnum;
import com.sugar.subscribe.course.module.business.controller.app.order.vo.*;
import com.sugar.subscribe.course.module.business.dal.dataobject.coach.CoachDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.coachplan.CoachPlanDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.serviceclass.ServiceClassDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.user.BizUserDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.venue.VenueDO;
import com.sugar.subscribe.course.module.business.dal.redis.objlock.ObjectLockRedisDAO;
import com.sugar.subscribe.course.module.business.enums.order.CancelStatus;
import com.sugar.subscribe.course.module.business.enums.order.RefundStatus;
import com.sugar.subscribe.course.module.business.job.order.dto.OrderCommissionSharingDTO;
import com.sugar.subscribe.course.module.business.job.order.dto.OrderConfirmDTO;
import com.sugar.subscribe.course.module.business.service.coach.CoachService;
import com.sugar.subscribe.course.module.business.service.coachplan.CoachPlanService;
import com.sugar.subscribe.course.module.business.service.coachwallet.CoachWalletService;
import com.sugar.subscribe.course.module.business.service.config.BizConfigService;
import com.sugar.subscribe.course.module.business.service.serviceclass.ServiceClassService;
import com.sugar.subscribe.course.module.business.service.user.BizUserService;
import com.sugar.subscribe.course.module.business.service.venue.VenueService;
import com.sugar.subscribe.course.module.business.util.AmountUtil;
import com.sugar.subscribe.course.module.pay.api.order.PayOrderApi;
import com.sugar.subscribe.course.module.pay.api.order.dto.PayOrderCreateReqDTO;
import com.sugar.subscribe.course.module.pay.api.order.dto.PayOrderRespDTO;
import com.sugar.subscribe.course.module.pay.api.order.dto.PayOrderSubmitReqDTO;
import com.sugar.subscribe.course.module.pay.api.order.dto.PayOrderSubmitRespDTO;
import com.sugar.subscribe.course.module.pay.api.refund.PayRefundApi;
import com.sugar.subscribe.course.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import com.sugar.subscribe.course.module.pay.api.refund.dto.PayRefundRespDTO;
import com.sugar.subscribe.course.module.pay.enums.order.PayOrderStatusEnum;
import com.sugar.subscribe.course.module.pay.enums.refund.PayRefundStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;

import com.sugar.subscribe.course.module.business.controller.admin.order.vo.*;
import com.sugar.subscribe.course.module.business.dal.dataobject.order.OrderDO;
import com.sugar.subscribe.course.framework.common.pojo.PageResult;
import com.sugar.subscribe.course.framework.common.util.object.BeanUtils;

import com.sugar.subscribe.course.module.business.dal.mysql.order.BizOrderMapper;

import static cn.hutool.core.util.ObjectUtil.notEqual;
import static com.sugar.subscribe.course.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.sugar.subscribe.course.framework.common.util.collection.CollectionUtils.convertList;
import static com.sugar.subscribe.course.framework.common.util.date.LocalDateTimeUtils.addTime;
import static com.sugar.subscribe.course.framework.common.util.json.JsonUtils.toJsonString;
import static com.sugar.subscribe.course.framework.common.util.servlet.ServletUtils.getClientIP;
import static com.sugar.subscribe.course.module.business.enums.ErrorCodeConstants.*;
import static com.sugar.subscribe.course.module.business.enums.config.BizConfigEnum.COACH_BROKERAGE;
import static com.sugar.subscribe.course.module.business.enums.order.OrderEnum.*;
import static com.sugar.subscribe.course.module.pay.enums.ErrorCodeConstants.PAY_ORDER_NOT_FOUND;
import static com.sugar.subscribe.course.module.pay.enums.PayChannelEnum.WX_LITE;

/**
 * 订单 Service 实现类
 *
 * @author Ysugar
 */
@Slf4j
@Service
@Validated
public class OrderServiceImpl implements OrderService {

    @Resource
    private BizOrderMapper orderMapper;

    @Resource
    private VenueService venueService;
    @Resource
    private ServiceClassService serviceClassService;
    @Resource
    private CoachService coachService;
    @Resource
    private BizUserService userService;
    @Resource
    private CoachPlanService coachPlanService;
    @Resource
    private BizConfigService configService;
    @Resource
    private CoachWalletService coachWalletService;

    @Resource
    private PayOrderApi payOrderApi;
    @Resource
    private PayRefundApi payRefundApi;

    @Resource
    private ObjectLockRedisDAO redisLockDAO;

    public static final String APP_KEY = "yk";

    @Override
    public void updateOrder(OrderSaveReqVO updateReqVO) {
        // 校验存在
        validateOrderExists(updateReqVO.getId());
        // 更新
        OrderDO updateObj = BeanUtils.toBean(updateReqVO, OrderDO.class);
        orderMapper.updateById(updateObj);
    }

    @Override
    public void updateOrderAmount(OrderAmountUpdateReqVO amountUpdateReqVO) {
        OrderDO order = validateOrderExists(amountUpdateReqVO.getId());
        // 判断订单是否已支付
        if (order.getPaymentStatus())
            throw exception(ORDER_IS_PAYED_NOT_UPDATE_AMOUNT);

        orderMapper.update(OrderDO::getId, amountUpdateReqVO.getId(), OrderDO::getPaymentAmount, amountUpdateReqVO.getPaymentAmount());
    }

    @Override
    public void orderAttendClass(OrderAttendClassReqVO amountUpdateReqVO) {
        OrderDO order = validateOrderExists(amountUpdateReqVO.getId());
        // 判断订单是否已经上课
        if (!order.getStatus().equals(WAITING_FOR_CLASS.getStatus()))
            throw exception(ORDER_NOT_ATTEND_CLASS);

        OrderDO updateObj = BeanUtils.toBean(amountUpdateReqVO, OrderDO.class);
        updateObj.setStatus(TO_BE_CONFIRMED.getStatus());

        // 更新
        orderMapper.updateById(updateObj);
    }

    @Override
    public void backOrderCancel(Long id) {
        OrderDO order = validateOrderExists(id);

        // 判断订单是否已完成
        if (order.getStatus().equals(DONE.getStatus()))
            throw exception(ORDER_IS_DONE_CAN_NOT_CANCEL);

        // 判断用户是否已支付，退款状态是否未退款
        if (order.getPaymentStatus() && order.getRefundStatus().equals(RefundStatus.NO_REFUND.getStatus())) {
            redisLockDAO.lock(id, 20000L, () -> {
                // 再次判断用户是否已退款
                OrderDetailRespVO lastOrder = getOrder(id);
                if (lastOrder.getRefundStatus().equals(RefundStatus.NO_REFUND.getStatus())){
                    // 创建退款单号
                    String refundOrderNo = "R" + lastOrder.getId();

                    // 创建退款单，并执行退款
                    PayRefundCreateReqDTO reqDTO = new PayRefundCreateReqDTO();
                    reqDTO
                            .setAppKey(APP_KEY)
                            .setUserIp(getClientIP())
                            .setUserId(lastOrder.getUserId())
                            .setUserType(UserTypeEnum.MEMBER.getValue())
                            .setMerchantOrderId(String.valueOf(lastOrder.getId()))
                            .setMerchantRefundId(refundOrderNo)
                            .setReason("管理员取消订单退款")
                            .setPrice(lastOrder.getPaymentAmount());
                    Long refundOrderId = payRefundApi.createRefund(reqDTO);

                    // 更新退款信息
                    orderMapper.update(Wrappers.<OrderDO>lambdaUpdate()
                            .eq(OrderDO::getId, id)
                            .set(OrderDO::getRefundStatus, RefundStatus.DOING.getStatus())
                            .set(OrderDO::getRefundOrderNo, refundOrderNo)
                            .set(OrderDO::getRefundOrderId, refundOrderId)
                            .set(OrderDO::getRefundAmount, lastOrder.getPaymentAmount())
                            .set(OrderDO::getRefundTime, LocalDateTime.now()));
                }
            });
        }

        // 更新订单状态
        orderMapper.update(Wrappers.<OrderDO>lambdaUpdate().eq(OrderDO::getId, id)
                .set(OrderDO::getCancelTime, LocalDateTime.now())
                .set(OrderDO::getCancelType, CancelStatus.SYSTEM.getStatus())
                .set(OrderDO::getStatus, CANCELLED.getStatus()));
    }

    @Override
    public void deleteOrder(Long id) {
        // 校验存在
        validateOrderExists(id);
        // 删除
        orderMapper.deleteById(id);
    }

    @Override
    public void deleteOrderListByIds(List<Long> ids) {
        // 删除
        orderMapper.deleteByIds(ids);
    }

    @Override
    public OrderDetailRespVO getOrder(Long id) {
        OrderDO order = validateOrderExists(id);
        OrderDetailRespVO respVO = BeanUtils.toBean(order, OrderDetailRespVO.class);

        // 设置教学分类
        respVO.setServiceIds(new Long[]{order.getServicePId(), order.getServiceId()});

        // 获取教练名称
        if (null != order.getCoachId()) {
            respVO.setCoachName(coachService.getCoachValidate(order.getCoachId()).getName());
        }

        return respVO;
    }


    private OrderDO validateOrderExists(Long id) {
        OrderDO orderDO = orderMapper.selectById(id);
        if (orderDO == null) {
            throw exception(ORDER_NOT_EXISTS);
        }
        return orderDO;
    }

    @Override
    public OrderDO getUserOrder(Long id, Long userId) {
        OrderDO order = validateOrderExists(id);

        if (userId != null) {
            if (!order.getUserId().equals(userId))
                throw exception(NO_PERMISSION);
        }

        return order;
    }

    @Override
    public OrderDO getCoachOrder(Long id, Long coachId) {
        OrderDO order = validateOrderExists(id);

        if (coachId != null) {
            Long dbCoachId = order.getCoachId();
            if (null == dbCoachId || !dbCoachId.equals(coachId))
                throw exception(NO_PERMISSION);
        }

        return order;
    }

    @Override
    public PageResult<OrderRespVO> getOrderPage(OrderPageReqVO pageReqVO) {
        return orderMapper.selectPage(pageReqVO);
    }

    // ======================================== 其它接口 ========================================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateOrderPaid(String merchantOrderId, Long payOrderId) {
        // 获取订单信息
        Long orderId = Long.valueOf(merchantOrderId);
        OrderDO order = validateOrderExists(orderId);

        // 校验并获得支付订单（可支付）
        PayOrderRespDTO payOrder = validateOrderCanPaid(merchantOrderId, payOrderId, order);

        // 更新订单为已支付
        orderMapper.update(OrderDO::getId, orderId,
                OrderDO::getPaymentStatus, true,
                OrderDO::getPaymentTime, LocalDateTime.now());
    }

    /**
     * 校验交易订单满足被支付的条件
     * <p>
     * 1. 交易订单未支付
     * 2. 支付单已支付
     *
     * @param merchantOrderId 交易订单编号
     * @param payOrderId      支付订单编号
     * @param order           订单信息
     * @return 交易订单
     */
    private PayOrderRespDTO validateOrderCanPaid(String merchantOrderId, Long payOrderId, OrderDO order) {
        // 1.1 校验订单未支付
        if (order.getPaymentStatus()) {
            log.error("[validateOrderCanPaid][order({}) 不处于待支付状态，请进行处理！order 数据是：{}]",
                    merchantOrderId, toJsonString(order));
            throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
        }
        // 1.2 校验支付订单匹配
        if (notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
            log.error("[validateOrderCanPaid][order({}) 支付单不匹配({})，请进行处理！order 数据是：{}]",
                    merchantOrderId, payOrderId, toJsonString(order));
            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
        }

        // 2.1 校验支付单是否存在
        PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
        if (payOrder == null) {
            log.error("[validateOrderCanPaid][order({}) payOrder({}) 不存在，请进行处理！]", merchantOrderId, payOrderId);
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        // 2.2 校验支付单已支付
        if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
            log.error("[validateOrderCanPaid][order({}) payOrder({}) 未支付，请进行处理！payOrder 数据是：{}]",
                    merchantOrderId, payOrderId, toJsonString(payOrder));
            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
        }
        // 2.3 校验支付金额一致
        if (notEqual(payOrder.getPrice(), order.getPaymentAmount())) {
            log.error("[validateOrderCanPaid][order({}) payOrder({}) 支付金额不匹配，请进行处理！order 数据是：{}，payOrder 数据是：{}]",
                    merchantOrderId, payOrderId, toJsonString(order), toJsonString(payOrder));
            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
        }
        // 2.4 校验支付订单匹配（二次）
        if (notEqual(payOrder.getMerchantOrderId(), merchantOrderId)) {
            log.error("[validateOrderCanPaid][order({}) 支付单不匹配({})，请进行处理！payOrder 数据是：{}]",
                    merchantOrderId, payOrderId, toJsonString(payOrder));
            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
        }
        return payOrder;
    }

    @Override
    public void updateOrderRefunded(String merchantRefundId, Long payRefundId) {
        // 1. 获得支付订单
        OrderDO order = orderMapper.selectOne(OrderDO::getRefundOrderNo, merchantRefundId);
        if (order == null) {
            log.error("[validateOrderCanRefunded][退款订单不存在 id({})]", merchantRefundId);
            throw exception(ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
        }

        // 1.1 校验退款订单是否已退款完成
        if (order.getRefundStatus().equals(RefundStatus.DONE.getStatus())) {
            log.error("[validateOrderCanRefunded][退款订单已退款完成 id({})]", merchantRefundId);
            return;
        }

        // 2. 校验并获得退款订单（可退款）
        PayRefundRespDTO payRefund = validateOrderCanRefunded(order, payRefundId);

        // 更新订单为已退款
        orderMapper.update(OrderDO::getId, order.getId(),
                OrderDO::getRefundStatus, RefundStatus.DONE.getStatus(),
                OrderDO::getRefundArrivalTime, LocalDateTime.now());
    }

    private PayRefundRespDTO validateOrderCanRefunded(OrderDO order, Long payRefundId) {
        // 1.1 校验退款订单匹配
        if (notEqual(order.getRefundOrderId(), payRefundId)) {
            log.error("[validateOrderCanRefunded][order({}) 退款单不匹配({})，请进行处理！order 数据是：{}]",
                    order.getId(), payRefundId, toJsonString(order));
            throw exception(ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
        }
        // 1.2 校验订单是否是退款中状态
        if (notEqual(order.getRefundStatus(), RefundStatus.DOING.getStatus())) {
            log.error("[validateOrderCanRefunded][order({}) 退款单非退款中状态({})，请进行处理！order 数据是：{}]",
                    order.getId(), payRefundId, toJsonString(order));
            throw exception(ORDER_REFUND_FAIL_REFUND_STATUS_ERROR);
        }

        // 2.1 校验退款订单
        PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
        if (payRefund == null) {
            log.error("[validateOrderCanRefunded][payRefund({})不存在]", payRefundId);
            throw exception(ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
        }
        // 2.2 判断是否退款失败
        if (PayRefundStatusEnum.isFailure(payRefund.getStatus())) {
            log.error("[validateOrderCanRefunded][payRefund({}) 退款失败，请进行处理！order 数据是：{}]",
                    payRefundId, toJsonString(order));
            throw exception(ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
        }
        // 2.3 校验退款金额一致
        if (notEqual(payRefund.getRefundPrice(), order.getRefundAmount())) {
            log.error("[validateOrderCanRefunded][order({}) payRefund({}) 退款金额不匹配，请进行处理！order 数据是：{}，payRefund 数据是：{}]",
                    order.getId(), payRefundId, toJsonString(order), toJsonString(payRefund));
            throw exception(ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
        }
        // 2.4 校验退款订单匹配（二次）
        if (notEqual(payRefund.getMerchantRefundId(), order.getRefundOrderNo())) {
            log.error("[validateOrderCanRefunded][order({}) 退款单不匹配({})，请进行处理！payRefund 数据是：{}]",
                    order.getId(), payRefundId, toJsonString(payRefund));
            throw exception(ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
        }
        return payRefund;
    }

    @Override
    public long autoConfirmOrder() {
        // 获取满足自动完成的订单的ID
        List<OrderConfirmDTO> orderDTOList = orderMapper.selectAutoConfirmOrderIds();
        long doneCount = 0;

        Integer commonCommissionRate = configService.getBizConfigByKey(COACH_BROKERAGE, Integer.class);

        for (OrderConfirmDTO orderDTO : orderDTOList) {
            try {
                // 获取教练佣金比例
                Integer commissionRate = orderDTO.getCommissionRate();

                // 判断若教练的佣金比例为空，则获取统一的佣金比例
                if (null == commissionRate)
                    commissionRate = commonCommissionRate;

                // 修改当前订单状态，并计算佣金
                updateOrderStatusDone(orderDTO.getId(), orderDTO.getPaymentAmount(), commissionRate);
                doneCount++;
            }catch (Throwable e){
                log.error("【自动确认订单任务】自动完成订单[{}]失败：", orderDTO.getId(), e);
            }
        }

        return orderDTOList.size() - doneCount;
    }

    @Override
    public long commissionSharingOrder() {
        List<OrderCommissionSharingDTO> orderDTOList = orderMapper.selectNotCommissionSharingOrder();
        long doneCount = 0;

        LocalDateTime nowDateTime = LocalDateTime.now();

        for (OrderCommissionSharingDTO orderDTO : orderDTOList) {
            try {
                // 给教练分佣
                coachWalletService.updateCoachWallet(orderDTO.getCoachId(), true, orderDTO.getProfitAmount());

                // 修改当前订单状态
                orderMapper.update(OrderDO::getId, orderDTO.getId(),
                        OrderDO::getProfitArrivalStatus, 1,
                        OrderDO::getProfitArrivalTime, nowDateTime);
                doneCount++;
            }catch (Throwable e){
                log.error("【自动分佣订单任务】分佣订单[{}]失败：", orderDTO.getId(), e);
            }
        }

        return orderDTOList.size() - doneCount;
    }

    // ======================================== APP 接口 ========================================

    private final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");

    private String genderOrderNo() {
        return LocalDateTime.now().format(FORMATTER) + RandomUtil.randomNumbers(3);
    }

    @Override
    public void appCreateOrder(AppOrderCreateReqVO createReqVO, Long userId) {
        Boolean orderType = createReqVO.getOrderType();
        Long planId = createReqVO.getPlanId();
        Long coachId = null;
        Integer paymentAmount;
        if (orderType) {
            if (null == planId)
                throw exception(COACH_PLAN_ID_NOT_NULL);

            // 获取教练课程信息
            CoachPlanDO coachPlan = coachPlanService.getCoachPlan(planId);

            // 根据课程信息获取教练信息，并判断教练是否在休息中，若是休息中，则默认不接单
            CoachDO coach = coachService.getCoachValidate(coachPlan.getCoachId());
            if (!coach.getIsWork())
                throw exception(COACH_REST);

            coachId = coach.getId();

            paymentAmount = coachPlan.getPrice();
        } else {
            // 将金额转换为分
            paymentAmount = AmountUtil.yuanToCent(createReqVO.getAmount());
        }

        ServiceClassDO serviceClass = serviceClassService.getServiceClass(createReqVO.getServiceId());
        // 判断子级分类是否属于这个父级分类
        if (!serviceClass.getParentId().equals(createReqVO.getServicePId()))
            throw exception(SERVICE_CLASS_NOT_MATCH);

        // 生成订单信息
        OrderDO order = BeanUtils.toBean(createReqVO, OrderDO.class);
        order
                .setCoachId(coachId)
                // 设置场地，服务类目等信息
                .setVenueName(venueService.getVenue(createReqVO.getVenueId()).getName())
                .setServicePName(serviceClassService.getServiceClass(createReqVO.getServicePId()).getName())
                .setServiceName(serviceClass.getName())
                // 设置基本信息
                .setOrderNo(genderOrderNo())
                .setUserId(userId)
                .setOrderTime(LocalDateTime.now())
                .setPaymentAmount(paymentAmount)
                .setOrderType(orderType);

        // 插入订单
        orderMapper.insert(order);
    }

    @Override
    public PayOrderSubmitRespDTO appUserPayOrder(Long id, Long userId, String clientIp) {
        // 获取用户信息
        BizUserDO user = userService.getBizUserValidate(userId);
        // 判断 openid 是否存在
        String openId = user.getOpenId();
        if (StrUtil.isBlank(openId))
            throw exception(BIZ_USER_OPEN_ID_IS_EMPTY);

        return redisLockDAO.lockGet(id, 30000L, () -> {
            OrderDO order = getUserOrder(id, userId);

            // 判断订单是否已经支付过
            if (order.getPaymentStatus())
                throw exception(ORDER_IS_PAY_SUCCESS);

            // 获取是否已经创建支付单，若未创建，则创建支付单
            Long payOrderId = order.getPayOrderId();
            if (null == payOrderId) {
                payOrderId = payOrderApi.createOrder(new PayOrderCreateReqDTO()
                        .setAppKey(APP_KEY).setUserIp(clientIp) // 支付应用
                        .setMerchantOrderId(String.valueOf(id)) // 业务的订单编号
                        .setSubject("用户约课")
                        .setBody(StrUtil.format("{} {}, {}-{}", order.getAttendClassDate(), order.getStartTime(), order.getVenueName(), order.getServiceName()))
                        .setPrice(order.getPaymentAmount()) // 价格信息
                        .setExpireTime(addTime(Duration.ofDays(7)))); // 支付的过期时间 7天

                // 更新订单表三方订单ID
                orderMapper.update(OrderDO::getId, order.getId(), OrderDO::getPayOrderId, payOrderId);
            }

            Map<String, String> channelExtras = new HashMap<>();
            channelExtras.put("openid", openId);

            PayOrderSubmitReqDTO reqDTO = new PayOrderSubmitReqDTO();
            reqDTO.setId(payOrderId)
                    .setChannelCode(WX_LITE.getCode())
                    .setChannelExtras(channelExtras);

            return payOrderApi.submitOrder(reqDTO, clientIp);
        });
    }

    @Override
    public void appUpdateOrder(AppOrderUpdateReqVO updateReqVO, Long userId) {
        OrderDO order = getUserOrder(updateReqVO.getId(), userId);

        // 判断订单是否已经上过课，上过课的就不能修改上课时间
        if (order.getStatus() > WAITING_FOR_CLASS.getStatus())
            throw exception(ORDER_ATTEND_CLASS_DONE_NOT_UPDATE_TIME);

        // 判断订单是否已经修改过上课时间
        if (order.getUpdatedAttendClassDate())
            throw exception(ORDER_ATTEND_CLASS_DATE_NOT_SECOND_UPDATE);

        LocalDate nowDate = LocalDate.now();
        LocalDate updateAttendClassDate = updateReqVO.getAttendClassDate();

        // 判断待修改的上课时间不能在当前时间之前
        if (updateAttendClassDate.isBefore(nowDate))
            throw exception(ORDER_ATTEND_CLASS_DATE_NOT_LESS_TODAY);

        LocalDate attendClassDate = order.getAttendClassDate();

        // 若当前时间 > 上课时间，则已经不能修改
        if (nowDate.isAfter(attendClassDate))
            throw exception(ORDER_NOT_UPDATE_ATTEND_CLASS_DATE);
        else if (ChronoUnit.DAYS.between(nowDate, attendClassDate) <= 3) // 判断距离上课时间是否低于3天，若低于3天不让修改
            throw exception(ORDER_ATTEND_CLASS_DATE_LESS_THAN_THREE);

        // 修改上课时间
        orderMapper.update(OrderDO::getId, order.getId(),
                OrderDO::getAttendClassDate, updateReqVO.getAttendClassDate(),
                OrderDO::getUpdatedAttendClassDate, true);
    }

    @Override
    public PageResult<AppUserOrderPageRespVO> appGetUserOrderPage(AppOrderPageReqVO reqVO, Long userId) {
        return orderMapper.appGetUserOrderPage(reqVO, userId, null, false);
    }

    @Override
    public PageResult<AppUserOrderPageRespVO> appGetCoachOrderPage(AppOrderPageReqVO reqVO, Long coachId) {
        return orderMapper.appGetUserOrderPage(reqVO, null, coachId, true);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void appUserCancelOrder(Long id, Long userId) {
        redisLockDAO.lock(id, 30000L, () -> {
            // 验证订单是否能取消
            OrderDO order = validOrderIsCanCancel(id, userId);

            LocalDateTime now = LocalDateTime.now();

            // 判断用户是否支付，未支付，则直接取消
            if (order.getPaymentStatus()) { // 用户已支付
                Integer refundStatus = order.getRefundStatus();
                // 判断订单是否是未退款状态，若是才执行退款操作，防止重复退款
                if (refundStatus.equals(RefundStatus.NO_REFUND.getStatus())) {
                    // 用于记录扣除比例
                    int deductRate = 0;

                    // 下单半小时内免费取消，判断下单是否超过半小时
                    if (ChronoUnit.MINUTES.between(order.getOrderTime(), now) > 30) {
                        // 计算扣除费用
                        LocalDate attendClassDate = order.getAttendClassDate();
                        long betweenDay = ChronoUnit.DAYS.between(LocalDate.now(), attendClassDate);

                        // 需扣除 100%
                        if (betweenDay <= 0) {
                            deductRate = 100;
                        } else if (betweenDay <= 1) {
                            // 提前1天取消订单：扣除费用70%
                            deductRate = 70;
                        } else {
                            // 提前3天取消订单：扣除费用50%
                            deductRate = 50;
                        }
                    } // else 免费取消，不扣手续费

                    // 获取用户支付金额
                    Integer paymentAmount = order.getPaymentAmount();

                    // 根据退款手续费计算退款金额
                    Integer refundAmount;
                    if (deductRate == 0) {
                        refundAmount = paymentAmount;
                    } else if (deductRate == 100) {
                        refundAmount = 0;
                    } else {
                        int deductAmount = paymentAmount * deductRate / 100;
                        refundAmount = paymentAmount - deductAmount;
                    }

                    boolean invokeRefundService = refundAmount > 0;

                    // 创建退款单号
                    String refundOrderNo = "R" + order.getId();
                    // 三方退款单号
                    Long refundOrderId = null;

                    // 当退款金额 > 0 时，才调用三方退款
                    if (invokeRefundService) {
                        // 创建退款单，并执行退款
                        PayRefundCreateReqDTO reqDTO = new PayRefundCreateReqDTO();
                        reqDTO
                                .setAppKey(APP_KEY)
                                .setUserIp(getClientIP())
                                .setUserId(userId)
                                .setUserType(UserTypeEnum.MEMBER.getValue())
                                .setMerchantOrderId(String.valueOf(order.getId()))
                                .setMerchantRefundId(refundOrderNo)
                                .setReason("用户手动取消订单退款")
                                .setPrice(refundAmount);
                        refundOrderId = payRefundApi.createRefund(reqDTO);
                    }

                    // 更新退款信息
                    orderMapper.update(Wrappers.<OrderDO>lambdaUpdate().eq(OrderDO::getId, id)
                            .set(OrderDO::getRefundStatus, invokeRefundService ? RefundStatus.DOING.getStatus() : RefundStatus.DONE.getStatus())
                            .set(OrderDO::getRefundOrderNo, refundOrderNo)
                            .set(OrderDO::getRefundOrderId, refundOrderId)
                            .set(OrderDO::getRefundAmount, refundAmount)
                            .set(OrderDO::getRefundTime, now)
                            .set(!invokeRefundService, OrderDO::getRefundArrivalTime, now));
                }
            } // else 用户未支付

            // 统一取消订单，更改订单状态
            orderMapper.update(Wrappers.<OrderDO>lambdaUpdate().eq(OrderDO::getId, id)
                    .set(OrderDO::getCancelTime, now)
                    .set(OrderDO::getCancelType, CancelStatus.USER.getStatus()).set(OrderDO::getStatus, CANCELLED.getStatus()));
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void appCoachCancelOrder(Long id, Long coachId) {
        redisLockDAO.lock(id, 30000L, () -> {
            OrderDO order = getCoachOrder(id, coachId);

            // 只有待接单或待上课状态，能取消订单
            Integer status = order.getStatus();
            boolean pendingOrder = status.equals(PENDING_ORDER.getStatus());
            boolean waitingForClass = status.equals(WAITING_FOR_CLASS.getStatus());
            if (!(pendingOrder || waitingForClass))
                throw exception(ORDER_COACH_CAN_NOT_CANCEL);

            // 获取订单类型
            Boolean orderType = order.getOrderType();

            // 待接单 & 直选教练约课类型
            if (pendingOrder && orderType) {
                // 直接取消订单，更新订单状态
                orderMapper.update(Wrappers.<OrderDO>lambdaUpdate()
                        .eq(OrderDO::getId, order.getId())
                        .set(OrderDO::getStatus, CANCELLED.getStatus())
                        .set(OrderDO::getCancelType, CancelStatus.COACH.getStatus())
                        .set(OrderDO::getCancelTime, LocalDateTime.now()));
            } else if (pendingOrder) { // 待接单 & 一键约课类型
                // 一键约课，不取消订单，将订单的教练Id更新为 null，等待后台再次分配
                orderMapper.update(OrderDO::getId, order.getId(), OrderDO::getCoachId, "");
                return;
            } else { // 待上课 & 直选教练约课类型 || 待上课 & 一键约课类型
                // 获取上课时间，并计算相差几天
                long betweenDay = ChronoUnit.DAYS.between(order.getAttendClassDate(), LocalDate.now());

                // 扣除违约金
                int liquidatedDamages = 0;

                if (betweenDay <= 0) { // 扣除全部违约金
                    liquidatedDamages = order.getPaymentAmount();
                } else if (betweenDay <= 3) { // 扣除 50%
                    liquidatedDamages = order.getPaymentAmount() / 2;
                }

                // 更新教练违约金扣除信息
                orderMapper.update(Wrappers.<OrderDO>lambdaUpdate()
                        .eq(OrderDO::getId, order.getId())
                        .set(OrderDO::getIsDeductLiquidatedDamages, liquidatedDamages > 0)
                        .set(OrderDO::getLiquidatedDamages, liquidatedDamages));
            }

            // 判断用户是否支付
            if (order.getPaymentStatus()) {
                // 若是已支付，则判断是否未退款，若是未退款，则执行退款
                if (order.getRefundStatus().equals(RefundStatus.NO_REFUND.getStatus())) {
                    // 创建退款单号
                    String refundOrderNo = "R" + order.getId();

                    // 创建退款单，并执行退款
                    PayRefundCreateReqDTO reqDTO = new PayRefundCreateReqDTO();
                    reqDTO
                            .setAppKey(APP_KEY)
                            .setUserIp(getClientIP())
                            .setUserId(order.getUserId())
                            .setUserType(UserTypeEnum.MEMBER.getValue())
                            .setMerchantOrderId(String.valueOf(order.getId()))
                            .setMerchantRefundId(refundOrderNo)
                            .setReason("教练不接单，订单退款")
                            .setPrice(order.getPaymentAmount());
                    Long refundOrderId = payRefundApi.createRefund(reqDTO);

                    // 更新退款信息
                    orderMapper.update(Wrappers.<OrderDO>lambdaUpdate().eq(OrderDO::getId, order.getId())
                            .set(OrderDO::getRefundStatus, RefundStatus.DOING.getStatus())
                            .set(OrderDO::getRefundOrderNo, refundOrderNo)
                            .set(OrderDO::getRefundOrderId, refundOrderId)
                            .set(OrderDO::getRefundAmount, order.getPaymentAmount())
                            .set(OrderDO::getRefundTime, LocalDateTime.now()));
                }
            }
        });
    }

    private OrderDO validOrderIsCanCancel(Long orderId, Long userId) {
        OrderDO order = getUserOrder(orderId, userId);

        Integer status = order.getStatus();

        // 判断订单状态是否已经上过课，若是上过课，则不能取消
        if (status > WAITING_FOR_CLASS.getStatus())
            throw exception(ORDER_ATTEND_CLASS_DONE_NO_CANCEL);

        // 判断订单是否已经取消
        if (status.equals(CANCELLED.getStatus()))
            throw exception(ORDER_IS_CANCELLED);

        return order;
    }

    @Override
    public void appDoneOrder(Long id, Long userId) {
        OrderDO order = getUserOrder(id, userId);

        // 判断订单是否是 待确认 状态
        if (!order.getStatus().equals(TO_BE_CONFIRMED.getStatus()))
            throw exception(ORDER_NOT_DONE);

        // 获取教练佣金比例
        CoachDO coach = coachService.getCoachValidate(order.getCoachId());
        Integer commissionRate = coach.getCommissionRate();

        // 判断若教练的佣金比例为空，则获取统一的佣金比例
        if (null == commissionRate)
            commissionRate = configService.getBizConfigByKey(COACH_BROKERAGE, Integer.class);

        // 更新订单状态，并计算佣金
        updateOrderStatusDone(id, order.getPaymentAmount(), commissionRate);
    }

    private void updateOrderStatusDone(Long id, Integer paymentAmount,Integer commissionRate) {
        if (null == commissionRate)
            commissionRate = 0;

        int profitAmount = 0;
        if (commissionRate > 0) {
            if (commissionRate > 100)
                commissionRate = 100;

            // 获取支付金额，计算佣金
            profitAmount = paymentAmount * commissionRate / 100;
        }

        // 更新状态为已完成
        orderMapper.update(Wrappers.<OrderDO>lambdaUpdate()
                .eq(OrderDO::getId, id)
                .set(OrderDO::getStatus, DONE.getStatus())
                .set(OrderDO::getDoneTime, LocalDateTime.now())
                // 记录教练预计收益金额
                .set(OrderDO::getProfitAmount, profitAmount)
                // 计算教练收益预计到账时间
                .set(OrderDO::getPredictProfitArrivalTime, LocalDateTime.now().plusDays(1)));
    }

    @Override
    public void appCoachTakeOrder(Long id, Long coachId) {
        OrderDO order = getCoachOrder(id, coachId);

        // 判断订单是否是 待接单 状态
        if (!order.getStatus().equals(PENDING_ORDER.getStatus()))
            throw exception(ORDER_NOT_TAKE_ORDER);

        // 判断用户是否支付
        if (!order.getPaymentStatus())
            throw exception(ORDER_NOT_PAY_NOT_TAKE);

        // 判断订单是否已经退款，若已退款则不能进行接单
        if (!order.getRefundStatus().equals(RefundStatus.NO_REFUND.getStatus()))
            throw exception(ORDER_IS_REFUND_NOT_TAKE);

        // 计算接单时长
        LocalDateTime orderTime = order.getOrderTime();
        LocalDateTime nowDateTime = LocalDateTime.now();

        long betweenMinutes = ChronoUnit.MINUTES.between(orderTime, nowDateTime);

        // 修改订单状态与接单时间
        orderMapper.update(Wrappers.<OrderDO>lambdaUpdate()
                .eq(OrderDO::getId, order.getId())
                .set(OrderDO::getStatus, WAITING_FOR_CLASS.getStatus())
                .set(OrderDO::getReceivedMinutes, betweenMinutes)
                .set(OrderDO::getReceivedTime, nowDateTime));

        // 更新教练的平均接单时长
        coachService.updateCoachReceivedOrderDuration(coachId);
    }

    @Override
    public void appCoachAttendClassOrder(AppOrderAttendClassReqVO appOrderAttendClassReqVO, Long coachId) {
        OrderDO order = getCoachOrder(appOrderAttendClassReqVO.getId(), coachId);

        // 判断订单是否是 待上课 状态
        if (!order.getStatus().equals(WAITING_FOR_CLASS.getStatus()))
            throw exception(ORDER_NOT_ATTEND_CLASS);

        // 判断用户是否支付
        if (!order.getPaymentStatus())
            throw exception(ORDER_NOT_PAY_NOT_ATTEND_CLASS);

        // 判断订单是否已经退款，若已退款则不能进行上课
        if (!order.getRefundStatus().equals(RefundStatus.NO_REFUND.getStatus()))
            throw exception(ORDER_IS_REFUND_DONE);

        OrderDO updateObj = BeanUtils.toBean(appOrderAttendClassReqVO, OrderDO.class);
        updateObj.setAttendClassTime(LocalDateTime.now()).setStatus(TO_BE_CONFIRMED.getStatus());

        // 更新订单状态与上课信息
        orderMapper.updateById(updateObj);
    }

    @Override
    public AppOrderDetailRespVO appGetOrder(Long id) {
        OrderDO orderDO = validateOrderExists(id);
        AppOrderDetailRespVO respVO = BeanUtils.toBean(orderDO, AppOrderDetailRespVO.class);

        // 转换金额
        respVO.setAmount(AmountUtil.centToYuan(orderDO.getPaymentAmount()));

        // 获取场地坐标信息
        VenueDO venue = venueService.getVenue(orderDO.getVenueId());
        if (Objects.nonNull(venue))
            respVO.setLocation(venue.getLocation());

        // 获取教练信息
        Long coachId = orderDO.getCoachId();
        if (null != coachId) {
            CoachDO coach = coachService.getCoach(coachId);
            if (null != coach) {
                respVO.setCoachNickname(coach.getNickname())
                        .setCoachAvatar(coach.getAvatar())
                        .setCoachMobile(coach.getMobile());
            }
        }

        return respVO;
    }

}